winsafe\gui\native_controls/edit.rs
1use std::any::Any;
2use std::marker::PhantomPinned;
3use std::pin::Pin;
4use std::sync::Arc;
5
6use crate::co;
7use crate::decl::*;
8use crate::gui::{privs::*, *};
9use crate::macros::*;
10use crate::msg::*;
11use crate::prelude::*;
12
13struct EditObj {
14 base: BaseCtrl,
15 events: BaseCtrlEvents,
16 _pin: PhantomPinned,
17}
18
19native_ctrl! { Edit: EditObj => GuiEventsEdit;
20 /// Native
21 /// [edit](https://learn.microsoft.com/en-us/windows/win32/controls/about-edit-controls)
22 /// (text box) control.
23}
24
25impl Edit {
26 /// Instantiates a new `Edit` object, to be created on the parent window
27 /// with [`HWND::CreateWindowEx`](crate::HWND::CreateWindowEx).
28 ///
29 /// # Panics
30 ///
31 /// Panics if the parent window was already created – that is, you cannot
32 /// dynamically create an `Edit` in an event closure.
33 ///
34 /// # Examples
35 ///
36 /// ```no_run
37 /// use winsafe::{self as w, prelude::*, gui};
38 ///
39 /// let wnd: gui::WindowMain; // initialized somewhere
40 /// # let wnd = gui::WindowMain::new(gui::WindowMainOpts::default());
41 ///
42 /// let txt = gui::Edit::new(
43 /// &wnd,
44 /// gui::EditOpts {
45 /// position: gui::dpi(10, 10),
46 /// width: gui::dpi_x(120),
47 /// ..Default::default()
48 /// },
49 /// );
50 /// ```
51 #[must_use]
52 pub fn new(parent: &(impl GuiParent + 'static), opts: EditOpts) -> Self {
53 let ctrl_id = auto_id::set_if_zero(opts.ctrl_id);
54 let new_self = Self(Arc::pin(EditObj {
55 base: BaseCtrl::new(ctrl_id),
56 events: BaseCtrlEvents::new(parent, ctrl_id),
57 _pin: PhantomPinned,
58 }));
59
60 let self2 = new_self.clone();
61 let parent2 = parent.clone();
62 let text2 = opts.text.to_owned();
63 parent
64 .as_ref()
65 .before_on()
66 .wm(parent.as_ref().wnd_ty().creation_msg(), move |_| {
67 self2.0.base.create_window(
68 opts.window_ex_style,
69 "EDIT",
70 Some(&text2),
71 opts.window_style | opts.control_style.into(),
72 opts.position.into(),
73 SIZE::with(opts.width, opts.height),
74 &parent2,
75 );
76 ui_font::set(self2.hwnd());
77 parent2
78 .as_ref()
79 .add_to_layout(self2.hwnd(), opts.resize_behavior);
80 Ok(0) // ignored
81 });
82
83 new_self.default_message_handlers(parent);
84 new_self
85 }
86
87 /// Instantiates a new `Edit` object, to be loaded from a dialog resource
88 /// with [`HWND::GetDlgItem`](crate::HWND::GetDlgItem).
89 ///
90 /// # Panics
91 ///
92 /// Panics if the parent dialog was already created – that is, you cannot
93 /// dynamically create an `Edit` in an event closure.
94 #[must_use]
95 pub fn new_dlg(
96 parent: &(impl GuiParent + 'static),
97 ctrl_id: u16,
98 resize_behavior: (Horz, Vert),
99 ) -> Self {
100 let new_self = Self(Arc::pin(EditObj {
101 base: BaseCtrl::new(ctrl_id),
102 events: BaseCtrlEvents::new(parent, ctrl_id),
103 _pin: PhantomPinned,
104 }));
105
106 let self2 = new_self.clone();
107 let parent2 = parent.clone();
108 parent.as_ref().before_on().wm_init_dialog(move |_| {
109 self2.0.base.assign_dlg(&parent2);
110 parent2
111 .as_ref()
112 .add_to_layout(self2.hwnd(), resize_behavior);
113 Ok(true) // ignored
114 });
115
116 new_self.default_message_handlers(parent);
117 new_self
118 }
119
120 fn default_message_handlers(&self, parent: &(impl GuiParent + 'static)) {
121 let self2 = self.clone();
122 let parent2 = parent.clone();
123 parent
124 .as_ref()
125 .before_on()
126 .wm_command(self.ctrl_id(), co::EN::CHANGE, move || {
127 // EN_CHANGE is first sent to the control before CreateWindowEx()
128 // returns, so if the user handles EN_CHANGE, the Edit HWND won't be
129 // set yet. So we set the HWND here.
130 if *self2.hwnd() == HWND::NULL {
131 let hctrl = parent2
132 .as_ref()
133 .hwnd()
134 .GetDlgItem(self2.ctrl_id())
135 .expect(DONTFAIL);
136 self2.0.base.set_hwnd(hctrl);
137 }
138 Ok(())
139 });
140 }
141
142 /// Hides any balloon tip by sending an
143 /// [`em::HideBalloonTip`](crate::msg::em::HideBalloonTip) message.
144 pub fn hide_balloon_tip(&self) -> SysResult<()> {
145 unsafe { self.hwnd().SendMessage(em::HideBalloonTip {}) }
146 }
147
148 /// Limits the number of characters that can be type by sending an
149 /// [`em::SetLimitText`](crate::msg::em::SetLimitText) message.
150 pub fn limit_text(&self, max_chars: Option<u32>) {
151 unsafe {
152 self.hwnd().SendMessage(em::SetLimitText { max_chars });
153 }
154 }
155
156 /// Replaces the currently selected text by sending an
157 /// [`em::ReplaceSel`](crate::msg::em::ReplaceSel) message.
158 pub fn replace_selection(&self, text: &str) {
159 let text16 = WString::from_str(text);
160 unsafe {
161 self.hwnd().SendMessage(em::ReplaceSel {
162 can_be_undone: true,
163 replacement_text: text16,
164 })
165 }
166 }
167
168 /// Sets the selection range of the text by sending an
169 /// [`em::SetSel`](crate::msg::em::SetSel) message.
170 ///
171 /// # Examples
172 ///
173 /// Selecting all text in the control:
174 ///
175 /// ```no_run
176 /// use winsafe::{self as w, prelude::*, gui};
177 ///
178 /// let my_edit: gui::Edit; // initialized somewhere
179 /// # let wnd = gui::WindowMain::new(gui::WindowMainOpts::default());
180 /// # let my_edit = gui::Edit::new(&wnd, gui::EditOpts::default());
181 ///
182 /// my_edit.set_selection(0, -1);
183 /// ```
184 ///
185 /// Clearing the selection:
186 ///
187 /// ```no_run
188 /// use winsafe::gui;
189 ///
190 /// let my_edit: gui::Edit; // initialized somewhere
191 /// # let wnd = gui::WindowMain::new(gui::WindowMainOpts::default());
192 /// # let my_edit = gui::Edit::new(&wnd, gui::EditOpts::default());
193 ///
194 /// my_edit.set_selection(-1, -1);
195 /// ```
196 pub fn set_selection(&self, start: i32, end: i32) {
197 unsafe {
198 self.hwnd().SendMessage(em::SetSel { start, end });
199 }
200 }
201
202 /// Sets the text by calling
203 /// [`HWND::SetWindowText`](crate::HWND::SetWindowText).
204 pub fn set_text(&self, text: &str) -> SysResult<()> {
205 self.hwnd().SetWindowText(text)?;
206 Ok(())
207 }
208
209 /// Displays a balloon tip by sending an
210 /// [`em::ShowBalloonTip`](crate::msg::em::ShowBalloonTip) message.
211 pub fn show_ballon_tip(&self, title: &str, text: &str, icon: co::TTI) -> SysResult<()> {
212 let mut title16 = WString::from_str(title);
213 let mut text16 = WString::from_str(text);
214
215 let mut info = EDITBALLOONTIP::default();
216 info.set_pszTitle(Some(&mut title16));
217 info.set_pszText(Some(&mut text16));
218 info.ttiIcon = icon;
219
220 unsafe { self.hwnd().SendMessage(em::ShowBalloonTip { info: &info }) }
221 }
222
223 /// Retrieves the text by calling
224 /// [`HWND::GetWindowText`](crate::HWND::GetWindowText).
225 #[must_use]
226 pub fn text(&self) -> SysResult<String> {
227 self.hwnd().GetWindowText()
228 }
229}
230
231/// Options to create an [`Edit`](crate::gui::Edit) programmatically with
232/// [`Edit::new`](crate::gui::Edit::new).
233pub struct EditOpts<'a> {
234 /// Text of the control to be
235 /// [created](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw).
236 ///
237 /// Defaults to empty string.
238 pub text: &'a str,
239 /// Left and top position coordinates of control within parent's client
240 /// area, to be
241 /// [created](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw).
242 ///
243 /// Defaults to `gui::dpi(0, 0)`.
244 pub position: (i32, i32),
245 /// Control width to be
246 /// [created](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw).
247 ///
248 /// Defaults to `gui::dpi_x(100)`.
249 pub width: i32,
250 /// Control height to be
251 /// [created](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw).
252 ///
253 /// Defaults to `gui::dpi_y(23)`.
254 ///
255 /// **Note:** You should change the default height only in a multi-line
256 /// edit, otherwise it will look off.
257 pub height: i32,
258 /// Edit styles to be
259 /// [created](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw).
260 ///
261 /// Defaults to `ES::AUTOHSCROLL | ES::NOHIDESEL`.
262 ///
263 /// Suggestions:
264 /// * add `ES::PASSWORD` for a password input;
265 /// * add `ES::NUMBER` to accept only numbers;
266 /// * replace with `ES::MULTILINE | ES::WANTRETURN | ES::AUTOVSCROLL | ES::NOHIDESEL` for a multi-line edit.
267 pub control_style: co::ES,
268 /// Window styles to be
269 /// [created](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw).
270 ///
271 /// Defaults to `WS::CHILD | WS::GROUP | WS::TABSTOP | WS::VISIBLE`.
272 pub window_style: co::WS,
273 /// Extended window styles to be
274 /// [created](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw).
275 ///
276 /// Defaults to `WS_EX::LEFT | WS_EX::CLIENTEDGE`.
277 pub window_ex_style: co::WS_EX,
278
279 /// The control ID.
280 ///
281 /// Defaults to an auto-generated ID.
282 pub ctrl_id: u16,
283 /// Horizontal and vertical behavior of the control when the parent window
284 /// is resized.
285 ///
286 /// **Note:** You should use `Vert::Resize` only in a multi-line edit.
287 ///
288 /// Defaults to `(gui::Horz::None, gui::Vert::None)`.
289 pub resize_behavior: (Horz, Vert),
290}
291
292impl<'a> Default for EditOpts<'a> {
293 fn default() -> Self {
294 Self {
295 text: "",
296 position: dpi(0, 0),
297 width: dpi_x(100),
298 height: dpi_y(23),
299 control_style: co::ES::AUTOHSCROLL | co::ES::NOHIDESEL,
300 window_style: co::WS::CHILD | co::WS::GROUP | co::WS::TABSTOP | co::WS::VISIBLE,
301 window_ex_style: co::WS_EX::LEFT | co::WS_EX::CLIENTEDGE,
302 ctrl_id: 0,
303 resize_behavior: (Horz::None, Vert::None),
304 }
305 }
306}